/**
 * 
 */
package jmine.tec.test.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import jmine.tec.proxy.chain.ChainElement;
import jmine.tec.proxy.chain.InvocationChain;

/**
 * Faz o 'lookup' de um metodo com a mesma assinatura do metodo sendo invocado em um delegate arbitrario. Se nenhum metodo é encontrado,
 * continua a invocacao do {@link InvocationChain}. O delegate nao precisa ter a mesma interface, apenas implementar um método com
 * assinatura equivalente ao metodo original.
 * 
 * @author takeshi
 */
public class MethodOverrideChainElement implements ChainElement {

    private final Object delegate;

    /**
     * C'tor
     * 
     * @param delegate o delegate a ser usado. Nao pode ser null;
     */
    public MethodOverrideChainElement(final Object delegate) {
        super();
        if (delegate == null) {
            throw new NullPointerException("delegate cannot be null");
        }
        this.delegate = delegate;
    }

    /**
     * @param chain the invocation chain
     * @return Object
     * @throws Throwable t
     * @see br.com.maps.util.proxy.chain.ChainElement#aroundInvoke(br.com.maps.util.proxy.chain.InvocationChain)
     */
    public Object aroundInvoke(final InvocationChain chain) throws Throwable {
        Method method = this.lookupEquals(chain.getInvokingMethod());
        if (method != null) {
            try {
                return method.invoke(this.delegate, chain.getParams());
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        }
        return chain.proceed();
    }

    /**
     * Procura no delegate
     * 
     * @param original the original method
     * @return {@link Method} or null if none found
     */
    private Method lookupEquals(final Method original) {
        try {
            Method m = this.delegate.getClass().getMethod(original.getName(), original.getParameterTypes());
            if (original.getReturnType().isAssignableFrom(m.getReturnType())) {
                return m;
            }
        } catch (NoSuchMethodException e) {
            return null;
        }
        return null;
    }

}
